/*
 * 微信公众平台(JAVA) SDK
 *
 * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
 *
 * http://www.weixin4j.org/sdk/
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations
 * under the License.
 */
package com.iwechat.mp.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.apache.http.HttpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.iwechat.httpclient.Attachment;
import com.iwechat.httpclient.HttpClientUtil;
import com.iwechat.httpclient.Response;
import com.iwechat.mp.service.task.WxmpTokenManager;
import com.iwechat.mpapi.Configuration;
import com.iwechat.mpapi.Data;
import com.iwechat.mpapi.Followers;
import com.iwechat.mpapi.Group;
import com.iwechat.mpapi.Menu;
import com.iwechat.mpapi.User;
import com.iwechat.mpapi.WeixinException;
import com.iwechat.mpapi.WeixinSupport;
import com.iwechat.mpapi.http.OAuthToken;
import com.iwechat.mpapi.message.Article;
import com.iwechat.mpapi.message.Articles;
import com.iwechat.mpapi.message.MediaType;
import com.iwechat.mpapi.pay.JsApiTicket;
import com.iwechat.mpapi.pay.UnifiedOrder;
import com.iwechat.mpapi.pay.UnifiedOrderResult;
import com.iwechat.mpapi.pay.redpack.SendRedPack;
import com.iwechat.mpapi.pay.redpack.SendRedPackResult;
import com.iwechat.mpapi.ticket.TicketType;
import com.iwechat.util.PropertyUtils;

/**
 * 微信公众平台API对象
 */
public class WxmpApi extends WeixinSupport {
    private static final Logger         LOG                       = LoggerFactory.getLogger(WxmpApi.class);
    private static final HttpClientUtil HTTPC                     = HttpClientUtil.getInstance();
    private static final WxmpApi        API                       = new WxmpApi();
    private static final PropertyUtils  CONFIG                    = PropertyUtils.getInstance();
    private static final String         API_TOKEN                 = "api.token";
    private static final String         CONF                      = "wxmpapi";
    private static final String         API_GETCALLBACKIP         = "api.getcallbackip";
    private static final String         API_USER_INFO             = "api.user.info";
    private static final String         API_USER_LIST             = "api.user.list";
    private static final String         API_GROUPS_CREATE         = "api.groups.create";
    private static final String         API_GROUPS_LIST           = "api.groups.list";
    private static final String         API_GROUPS_GETID          = "api.groups.getid";
    private static final String         API_GROUPS_UPDATE         = "api.groups.update";
    private static final String         API_GROUPS_DELETE         = "api.groups.delete";
    private static final String         API_GROUPS_MEMBERS_UPDATE = "api.groups.members.update";
    private static final String         API_MENU_CREATE           = "api.menu.create";
    private static final String         API_MENU_GET              = "api.menu.get";
    private static final String         API_MENU_DELETE           = "api.menu.delete";
    private static final String         API_QRCODE_CREATE         = "api.qrcode.create";
    private static final String         API_SHOWQRCODE            = "api.showqrcode";
    private static final String         API_MESSAGE_MASS_SEND     = "api.message.mass.send";
    private static final String         API_MESSAGE_CUSTOM_SEND   = "api.message.custom.send";
    private static final String         API_MESSAGE_TEMPLATE_SEND = "api.message.template.send";
    private static final String         API_MEDIA_UPLOAD          = "api.media.upload";
    private static final String         API_MEDIA_UPLOADNEWS      = "api.media.uploadnews";
    private static final String         API_MEDIA_GET             = "api.media.get";
    private static final String         API_TICKET_GETTICKET      = "api.ticket.getticket";
    private static final String         API_PAY_UNIFIEDORDER      = "api.pay.unifiedorder";
    private static final String         API_SENDREDPACK           = "api.mmpaymkttransfers.sendredpack";

    /**
     * 获取对象实例<br>
     *
     * @return the instance
     */
    public static WxmpApi getAPI() {
        return API;
    }

    /**
     * 微信基础支持
     *
     * 默认Access_Token过期后会自动重新登录
     */
    private WxmpApi() {
    }

    /**
     * 获取登录后的OAuthToken对象
     *
     * @return 如果已登录过返回OAuthToken对象，否则返回null
     */
    public OAuthToken getOAuthToken() {
        return WxmpTokenManager.getoAuthToken();
    }

    /**
     * 获取access_token
     *
     * @param appId
     *            第三方用户唯一凭证
     * @param secret
     *            第三方用户唯一凭证密钥，既appsecret
     * @return 用户凭证
     * @throws Exception
     * @since Weixin4J 1.0.0
     */
    public OAuthToken login(String appId, String secret) throws Exception {
        return login(appId, secret, "client_credential");
    }

    /**
     * 向微信平台发送获取access_token请求
     *
     * @param appId
     *            第三方用户唯一凭证
     * @param secret
     *            第三方用户唯一凭证密钥，既appsecret
     * @param grantType
     *            获取access_token填写client_credential
     * @return 用户凭证
     * @throws Exception
     * @since Weixin4J 1.0.0
     */
    private OAuthToken login(String appId, String secret, String grantType) throws Exception {
        OAuthToken newToken = null;
        if (appId == null || secret == null || appId.equals("") || secret.equals("") || grantType == null || grantType.equals("")) {
            throw new WeixinException("invalid null, appid or secret is null.");
        }
        // 拼接参数
        String param = "?grant_type=" + grantType + "&appid=" + appId + "&secret=" + secret;
        // 创建请求对象
        // 调用获取access_token接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_TOKEN) + param, null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("login返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            // 判断是否登录成功，并判断过期时间
            Object obj = jsonObj.get("access_token");
            // 登录成功，设置accessToken和过期时间
            if (obj != null) {
                newToken = JSON.toJavaObject(jsonObj, OAuthToken.class);
            }
        }
        return newToken;
    }

    /**
     * 根据OpenId获取用户对象
     *
     * <p>
     * 通过公众号，返回用户对象，进行用户相关操作
     * </p>
     *
     * @param openId
     *            普通用户的标识，对当前公众号唯一
     * @return 用户对象
     * @throws Exception
     */
    public User getUserInfo(String openId) throws Exception {
        // 默认简体中文
        return getUserInfo(openId, "zh_CN");
    }

    /**
     * 获取用户对象
     *
     * <p>
     * 通过公众号，返回用户对象，进行用户相关操作
     * </p>
     *
     * @param openId
     *            普通用户的标识，对当前公众号唯一
     * @param lang
     *            国家地区语言版本 zh_CN 简体，zh_TW 繁体，en 英语
     * @return 用户对象
     * @throws Exception
     */
    public User getUserInfo(String openId, String lang) throws Exception {
        // 拼接参数

        String param = "?access_token=" + this.getOAuthToken().getAccess_token() + "&openid=" + openId + "&lang=" + lang;
        // 调用获取access_token接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_USER_INFO) + param, null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("getUserInfo返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            // 设置公众号信息
            return JSON.toJavaObject(jsonObj, User.class);
        }
        return null;
    }

    /**
     * 获取所有关注者列表
     *
     * <p>
     * 通过公众号，返回用户对象，进行用户相关操作
     * </p>
     *
     * @return 关注者对象
     * @throws Exception
     */
    public Followers getAllUserList() throws Exception {
        Followers allFollower = new Followers();
        Data data = new Data();
        data.setOpenid(new ArrayList<String>());
        allFollower.setData(data);
        String next_openid = "";
        do {
            Followers f = getUserList(next_openid);
            if (f == null) {
                break;
            }
            if (f.getCount() > 0) {
                List<String> openids = f.getData().getOpenid();
                for (String openid : openids) {
                    allFollower.getData().getOpenid().add(openid);
                }
            }
            next_openid = f.getNext_openid();
        } while (!next_openid.equals(""));
        allFollower.setTotal(allFollower.getData().getOpenid().size());
        allFollower.setCount(allFollower.getTotal());
        allFollower.setNext_openid("");
        return allFollower;
    }

    /**
     * 获取关注者列表
     *
     * <p>
     * 通过公众号，返回用户对象，进行用户相关操作
     * </p>
     *
     * @param next_openid
     *            第一个拉取的OPENID，不填默认从头开始拉取
     * @return 关注者对象
     * @throws Exception
     */
    public Followers getUserList(String next_openid) throws Exception {
        // 拼接参数
        String param = "?access_token=" + this.getOAuthToken().getAccess_token() + "&next_openid=";
        // 第一次获取不添加参数
        if (next_openid != null && !next_openid.equals("")) {
            param += next_openid;
        }
        // 调用获取access_token接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_USER_LIST) + param, null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        Followers follower = null;
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("getUserList返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            follower = JSON.toJavaObject(jsonObj, Followers.class);
        }
        return follower;
    }

    /**
     * 创建分组
     *
     * @param name
     *            分组名字（30个字符以内）
     * @return 创建成功，返回带Id的Group对象
     * @throws Exception
     */
    public Group createGroup(String name) throws Exception {
        // 内部业务验证
        if (name == null || name.equals("")) {
            throw new IllegalStateException("name is null!");
        }
        // 拼接参数
        JSONObject postGroup = new JSONObject();
        JSONObject postName = new JSONObject();
        postName.put("name", name);
        postGroup.put("group", postName);
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_CREATE) + this.getOAuthToken().getAccess_token(),
                postGroup.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        Group group = null;
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("createGroup返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            JSONObject jsonGroup = jsonObj.getJSONObject("group");
            group = JSON.toJavaObject(jsonGroup, Group.class);
        }
        return group;
    }

    /**
     * 查询所有分组
     *
     * <p>
     * 最多支持创建500个分组
     * </p>
     *
     * @return 分组列表
     * @throws Exception
     */
    public List<Group> getGroups() throws Exception {
        List<Group> groupList = new ArrayList<Group>();
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_LIST) + this.getOAuthToken().getAccess_token(), null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("getGroups返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            // 获取分组列表
            JSONArray groups = jsonObj.getJSONArray("groups");
            for (int i = 0; i < groups.size(); i++) {
                JSONObject jsonGroup = groups.getJSONObject(i);
                Group group = JSON.toJavaObject(jsonGroup, Group.class);
                groupList.add(group);
            }
        }
        return groupList;
    }

    /**
     * 查询用户所在分组
     *
     * <p>
     * 通过用户的OpenID查询其所在的GroupID
     * </p>
     *
     * @param openid
     *            用户唯一标识符
     * @return 返回用户所在分组Id
     * @throws Exception
     */
    public int getGroupId(String openid) throws Exception {
        // 内部业务验证
        if (openid == null || openid.equals("")) {
            throw new IllegalStateException("openid is null!");
        }
        int groupId = -1;
        // 拼接参数
        JSONObject postParam = new JSONObject();
        postParam.put("openid", openid);
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_GETID) + this.getOAuthToken().getAccess_token(),
                postParam.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("getGroupId返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            // 获取成功返回分组Id
            groupId = jsonObj.getIntValue("groupid");
        }
        return groupId;
    }

    /**
     * 修改分组名
     *
     * @param id
     *            分组id，由微信分配
     * @param name
     *            分组名字（30个字符以内）
     * @throws Exception
     */
    public void updateGroup(int id, String name) throws Exception {
        // 内部业务验证
        if (id < 0) {
            throw new IllegalStateException("id can not <= 0!");
        }
        if (name == null || name.equals("")) {
            throw new IllegalStateException("name is null!");
        }
        // 拼接参数
        JSONObject postGroup = new JSONObject();
        JSONObject postName = new JSONObject();
        postName.put("id", id);
        postName.put("name", name);
        postGroup.put("group", postName);
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_UPDATE) + this.getOAuthToken().getAccess_token(),
                postGroup.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("updateGroup返回json：" + jsonObj.toString());
            }
            // 判断是否修改成功
            // 正常时返回 {"errcode": 0, "errmsg": "ok"}
            // 错误时返回 示例：{"errcode":40013,"errmsg":"invalid appid"}
            int errcode = jsonObj.getIntValue("errcode");
            // 登录成功，设置accessToken和过期时间
            if (errcode != 0) {
                // 返回异常信息
                throw new WeixinException(getCause(errcode));
            }
        }
    }

    /**
     * 删除分组
     *
     * @param groupId
     *            分组Id
     * @throws Exception
     */
    public void deleteGroup(int groupId) throws Exception {
        // 拼接参数
        JSONObject postParam = new JSONObject();
        JSONObject group = new JSONObject();
        group.put("id", groupId);
        postParam.put("group", group);
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_DELETE) + this.getOAuthToken().getAccess_token(),
                postParam.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("deleteGroup返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
        }
    }

    /**
     * 移动用户分组
     *
     * @param openid
     *            用户唯一标识符
     * @param to_groupid
     *            分组id
     * @throws Exception
     */
    public void updateMemberGroup(String openid, int to_groupid) throws Exception {
        // 内部业务验证
        if (openid == null || openid.equals("")) {
            throw new IllegalStateException("openid is null!");
        }
        if (to_groupid < 0) {
            throw new IllegalStateException("to_groupid can not <= 0!");
        }
        // 拼接参数
        JSONObject postParam = new JSONObject();
        postParam.put("openid", openid);
        postParam.put("to_groupid", to_groupid);
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_GROUPS_MEMBERS_UPDATE) + this.getOAuthToken().getAccess_token(),
                postParam.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("updateMemberGroup返回json：" + jsonObj.toString());
            }
            // 判断是否修改成功
            // 正常时返回 {"errcode": 0, "errmsg": "ok"}
            // 错误时返回 示例：{"errcode":40013,"errmsg":"invalid appid"}
            int errcode = jsonObj.getIntValue("errcode");
            // 登录成功，设置accessToken和过期时间
            if (errcode != 0) {
                // 返回异常信息
                throw new WeixinException(getCause(errcode));
            }
        }
    }

    /**
     * 创建自定义菜单
     *
     * @param menu
     *            菜单对象
     * @throws Exception
     */
    public void createMenu(Menu menu) throws Exception {
        // 内部业务验证
        if (menu == null || menu.getButton() == null) {
            throw new IllegalStateException("menu is null!");
        }
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MENU_CREATE) + this.getOAuthToken().getAccess_token(),
                menu.toJSONObject().toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("createMenu返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
        }
    }

    /**
     * 查询自定义菜单
     *
     * <p>
     * 最多支持创建2级自定义菜单
     * </p>
     *
     * @return 自定义菜单对象
     * @throws Exception
     */
    public Menu getMenu() throws Exception {
        // 调用获取access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MENU_GET) + this.getOAuthToken().getAccess_token(), null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("getMenu返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            // 返回自定义菜单对象
            return new Menu(jsonObj);
        }
        // 返回自定义菜单对
        return null;
    }

    /**
     * 删除自定义菜单
     *
     * @throws Exception
     */
    public void deleteMenu() throws Exception {
        // 调用获取access_token接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_MENU_DELETE) + this.getOAuthToken().getAccess_token(), null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("deleteMenu返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
        }
    }

    /**
     * 创建二维码ticket
     *
     * @param ticketType
     *            二维码类型
     * @param fileName
     *            图片文件路径
     * @param scene_id
     *            场景值ID
     * @param expire_seconds
     *            临时二维码过期时间
     * @throws Exception
     */
    public void createQrcode(TicketType ticketType, String fileName, int scene_id, int expire_seconds) throws Exception {
        // 内部业务验证
        if (ticketType.equals(TicketType.QR_SCENE)) {
            if (scene_id <= 0) {
                throw new IllegalStateException("场景id不能小于等于0");
            }
        } else if (ticketType.equals(TicketType.QR_LIMIT_SCENE)) {
            if (scene_id <= 0 || scene_id > 100000) {
                throw new IllegalStateException("永久场景id参数只支持1-100000");
            }
        }
        JSONObject ticketJson = new JSONObject();
        if (ticketType.equals(TicketType.QR_SCENE)) {
            // 临时二维码过期时间
            ticketJson.put("expire_seconds", expire_seconds);
        }
        // 二维码类型
        ticketJson.put("action_name", ticketType.toString());

        JSONObject actionInfo = new JSONObject();
        JSONObject scene = new JSONObject();
        scene.put("scene_id", scene_id);
        actionInfo.put("scene", scene);
        // 二维码详细信息
        ticketJson.put("action_info", actionInfo);
        // 调用创建Tick的access_token接口
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_QRCODE_CREATE) + this.getOAuthToken().getAccess_token(),
                ticketJson.toJSONString(), null);
        // 根据请求结果判定，返回结果
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("createQrcode返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                try {
                    // 通过ticket换取二维码
                    URL url = new URL(CONFIG.getStringValue(CONF, API_SHOWQRCODE) + jsonObj.getString("ticket"));
                    // 打开连接
                    URLConnection con = url.openConnection();
                    // 输入流
                    InputStream is = con.getInputStream();
                    // 1K的数据缓冲
                    byte[] bs = new byte[1024];
                    // 读取到的数据长度
                    int len;
                    // 输出的文件流
                    OutputStream os = new FileOutputStream(fileName);
                    // 开始读取
                    while ((len = is.read(bs)) != -1) {
                        os.write(bs, 0, len);
                    }
                    // 完毕，关闭所有链接
                    os.close();
                    is.close();
                } catch (MalformedURLException ex) {
                    throw new WeixinException("通过ticket换取二维码异常：", ex);
                } catch (IOException ex) {
                    throw new WeixinException("通过ticket换取二维码，下载二维码图片异常：", ex);
                }
            }
        }
    }

    /**
     * 根据OpenID列表群发文本消息
     *
     * @param openIds
     *            粉丝OpenId集合
     * @param txtContent
     *            文本消息内容
     * @return 发送成功则返回群发消息Id，否则返回null
     * @throws Exception
     */
    public String massSendContent(String[] openIds, String txtContent) throws Exception {
        JSONObject json = new JSONObject();
        JSONObject text = new JSONObject();
        text.put("content", txtContent);
        json.put("touser", openIds);
        json.put("text", text);
        json.put("msgtype", "text");
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MESSAGE_MASS_SEND) + this.getOAuthToken().getAccess_token(),
                json.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("群发文本消息返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                // 返回群发消息id
                return jsonObj.getString("msg_id");
            }
        }
        return null;
    }

    /**
     * 根据OpenID列表群发文本消息
     *
     * @param openIds
     *            粉丝OpenId集合
     * @param mediaId
     *            图文消息素材Id
     * @return 发送成功则返回群发消息Id，否则返回null
     * @throws Exception
     */
    public String massSendNews(String[] openIds, String mediaId) throws Exception {
        JSONObject json = new JSONObject();
        JSONObject media_id = new JSONObject();
        media_id.put("media_id", mediaId);
        json.put("touser", openIds);
        json.put("mpnews", media_id);
        json.put("msgtype", "mpnews");
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MESSAGE_MASS_SEND) + this.getOAuthToken().getAccess_token(),
                json.toJSONString(), null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("群发图文消息返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                // 返回群发图文id
                return jsonObj.getString("msg_id");
            }
        }
        return null;
    }

    /**
     * 上传图文消息素材
     *
     * @param articles
     *            图文消息，一个图文消息支持1到10条图文
     * @return 上传成功返回图文素材Id，否则返回null
     * @throws Exception
     */
    public String uploadnews(List<Article> articles) throws Exception {
        JSONObject json = new JSONObject();
        json.put("articles", articles);
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MEDIA_UPLOADNEWS) + this.getOAuthToken().getAccess_token(), json.toJSONString(),
                null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("uploadnews返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                // 返回图文消息id
                return jsonObj.getString("media_id");
            }
        }
        return null;
    }

    /**
     * 发送客服文本消息
     *
     * @param openId
     *            粉丝OpenId
     * @param txtContent
     *            文本消息内容
     * @throws Exception
     */
    public void customSendContent(String openId, String txtContent) throws Exception {
        JSONObject json = new JSONObject();
        JSONObject text = new JSONObject();
        text.put("content", txtContent);
        json.put("touser", openId);
        json.put("text", text);
        json.put("msgtype", "text");
        HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MESSAGE_CUSTOM_SEND) + this.getOAuthToken().getAccess_token(), json.toJSONString(), null);
    }

    /**
     * 发送客服文本消息
     *
     * @param templateJson
     *            模板消息json字符串
     * @param seqnb
     *            消息序号
     * @throws Exception
     */
    public JSONObject templateMessageSend(String templateJson, long seqnb) throws Exception {
        Response resp = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MESSAGE_TEMPLATE_SEND) + this.getOAuthToken().getAccess_token(), templateJson,
                String.valueOf(seqnb));
        if (resp != null && resp.getStatus() == 200) {
            return JSON.parseObject(resp.getDataStr());
        } else {
            JSONObject other = new JSONObject();
            other.put("errcode", -1);
            other.put("errmsg", "http error");
            other.put("msgid", -1);
            return other;
        }
    }

    /**
     * 发送客服图文消息
     *
     * @param openId
     *            粉丝OpenId
     * @param articles
     *            图文消息，一个图文消息支持1到10条图文
     * @throws Exception
     */
    public void customSendNews(String openId, List<Articles> articles) throws Exception {
        JSONObject json = new JSONObject();
        json.put("touser", openId);
        json.put("msgtype", "news");
        JSONObject news = new JSONObject();
        news.put("articles", articles);
        json.put("news", news);
        HTTPC.sendPost(CONFIG.getStringValue(CONF, API_MESSAGE_CUSTOM_SEND) + this.getOAuthToken().getAccess_token(), json.toJSONString(), null);
    }

    /**
     * 新增临时素材
     *
     * @param mediaType
     *            媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
     * @param file
     *            form-data中媒体文件标识，有filename、filelength、content-type等信息
     * @return 上传成功返回素材Id，否则返回null
     * @throws WeixinException
     * @throws HttpException
     */
    public String uploadMedia(MediaType mediaType, File file) throws WeixinException, HttpException {
        // 上传素材，返回JSON数据包
        String jsonStr = HTTPC.upload(
                CONFIG.getStringValue(CONF, API_MEDIA_UPLOAD) + this.getOAuthToken().getAccess_token() + "&type=" + mediaType.toString(), file);
        JSONObject jsonObj = JSON.parseObject(jsonStr);
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("上传多媒体文件返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                // 返回多媒体文件id
                return jsonObj.getString("media_id");
            }
        }
        return null;
    }

    /**
     * 获取临时素材
     *
     * @param mediaId
     *            媒体文件ID
     * @return 正确返回附件对象，否则返回null
     * @throws WeixinException
     * @throws HttpException
     */
    public Attachment downloadMedia(String mediaId) throws WeixinException, HttpException {
        // 下载资源
        String url = CONFIG.getStringValue(CONF, API_MEDIA_GET) + this.getOAuthToken().getAccess_token() + "&media_id=" + mediaId;
        return HTTPC.download(url);
    }

    /**
     * 获取jsapi_ticket对象，每次都返回最新
     *
     * @return 成功返回jsapi_ticket对象，失败返回NULL
     * @throws Exception
     */
    public JsApiTicket getJsApi_Ticket() throws Exception {
        // 调用获取jsapi_ticket接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_TICKET_GETTICKET) + this.getOAuthToken().getAccess_token() + "&type=jsapi", null,
                null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        // 成功返回如下JSON:
        // {"errcode":0,"errmsg":"ok","ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA","expires_in":7200}
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("获取jsapi_ticket返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                return new JsApiTicket(jsonObj.getString("ticket"), jsonObj.getIntValue("expires_in"));
            }
        }
        return null;
    }

    /**
     * 统一下单
     *
     * @param unifiedorder
     *            统一下单对象
     * @return 下单返回结果对象
     * @throws Exception
     */
    public UnifiedOrderResult payUnifiedOrder(UnifiedOrder unifiedorder) throws Exception {
        // 将统一下单对象转成XML
        String xmlPost = unifiedorder.toXML();
        if (Configuration.isDebug()) {
            LOG.info("调试模式_统一下单接口 提交XML数据：" + xmlPost);
        }
        // 提交xml格式数据
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_PAY_UNIFIEDORDER), xmlPost, null);
        // 获取微信平台下单接口返回数据
        String xmlResult = res.asString();
        try {
            JAXBContext context = JAXBContext.newInstance(UnifiedOrderResult.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            UnifiedOrderResult result = (UnifiedOrderResult) unmarshaller.unmarshal(new StringReader(xmlResult));
            return result;
        } catch (JAXBException ex) {
            return null;
        }
    }

    /**
     * 发送现金红包
     *
     * @param sendRedPack
     *            现金红包对象
     * @return 发送现金红包返回结果对象
     * @throws Exception
     */
    public SendRedPackResult sendRedPack(SendRedPack sendRedPack) throws Exception {
        // 将统一下单对象转成XML
        String xmlPost = sendRedPack.toXML();
        if (Configuration.isDebug()) {
            LOG.info("调试模式_发送现金红包接口 提交XML数据：" + xmlPost);
        }
        // 提交xml格式数据
        Response res = HTTPC.sendPost(CONFIG.getStringValue(CONF, API_SENDREDPACK), xmlPost, null);
        // 获取微信平台下单接口返回数据
        String xmlResult = res.asString();
        try {
            JAXBContext context = JAXBContext.newInstance(SendRedPackResult.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            SendRedPackResult result = (SendRedPackResult) unmarshaller.unmarshal(new StringReader(xmlResult));
            return result;
        } catch (JAXBException ex) {
            return null;
        }
    }

    /**
     * 获取微信服务器IP地址
     *
     * @return 微信服务器IP地址列表
     * @throws Exception
     */
    public Object[] getCallbackIp() throws Exception {
        // 调用获取jsapi_ticket接口
        Response res = HTTPC.sendGet(CONFIG.getStringValue(CONF, API_GETCALLBACKIP) + this.getOAuthToken().getAccess_token(), null, null);
        // 根据请求结果判定，是否验证成功
        JSONObject jsonObj = res.asJSONObject();
        // 成功返回如下JSON:
        // {"ip_list":["127.0.0.1","127.0.0.1"]}
        if (jsonObj != null) {
            if (Configuration.isDebug()) {
                LOG.info("获取getCallbackIp返回json：" + jsonObj.toString());
            }
            Object errcode = jsonObj.get("errcode");
            if (errcode != null && !errcode.toString().equals("0")) {
                // 返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            } else {
                JSONArray ipList = jsonObj.getJSONArray("ip_list");
                if (ipList != null) {
                    // 转换为List
                    return ipList.toArray();
                }
            }
        }
        return null;
    }
}
